Skip to content

feat: EXPIREAT, PEXPIREAT, GETSET, MSETNX, LPOP/RPOP count - #318

Merged
kacy merged 1 commit into
mainfrom
feat/expireat-getset-msetnx-lpop-count
Feb 26, 2026
Merged

feat: EXPIREAT, PEXPIREAT, GETSET, MSETNX, LPOP/RPOP count#318
kacy merged 1 commit into
mainfrom
feat/expireat-getset-msetnx-lpop-count

Conversation

@kacy

@kacy kacy commented Feb 26, 2026

Copy link
Copy Markdown
Owner

summary

adds six commands that fill in the remaining gaps in common string and list operations:

  • EXPIREAT — set a key's expiry to an absolute unix timestamp in seconds
  • PEXPIREAT — same but in milliseconds
  • GETSET — atomically get the current value and replace it with a new one; clears any existing ttl
  • MSETNX — set multiple key/value pairs atomically only if none of the keys already exist
  • LPOP key [count] — optional count argument; without count returns a bulk string, with count returns an array
  • RPOP key [count] — same semantics

what was tested

  • 577 unit tests pass (cargo test -p emberkv-core)
  • 412 protocol tests pass (cargo test -p ember-protocol)
  • 12 new integration tests covering all six commands including edge cases:
    • expireat_sets_absolute_expiry, expireat_missing_key_returns_zero
    • pexpireat_sets_absolute_expiry_ms
    • getset_returns_old_value, getset_missing_key_returns_nil
    • msetnx_all_new_returns_one, msetnx_any_existing_returns_zero_and_no_changes
    • lpop_no_count_returns_bulk_string, lpop_count_returns_array, lpop_count_exceeding_list_returns_all
    • rpop_count_returns_array
  • cargo clippy -D warnings clean on all affected crates

design notes

EXPIREAT/PEXPIREAT time model: ember stores expiry as a monotonic ms offset from process start, not unix epoch. a new unix_ms_to_monotonic_ms() function provides the inverse of the existing monotonic_to_unix_ms(), sharing a single ClockAnchor captured at startup so both conversions are coherent. EXPIREAT is persisted to AOF as Pexpireat (absolute unix ms); on recovery the remaining TTL is computed from wall time at that instant.

MSETNX fan-out: unlike MSET (which fans out individual Set requests), MSETNX needs to check all keys before writing any. we do two passes via route_multi: first Exists across all shards, then Set for each pair if all were absent. this is not atomic across shards in the same way redis isn't atomic across hash slots in cluster mode, but it is correct for single-node and cluster-mode when keys share a slot.

LPOP/RPOP count dispatch: count=None reuses the existing LPop/RPop shard variants (returning ShardResponse::Value) while count=Some(n) uses new LPopCount/RPopCount variants (returning ShardResponse::Array). this keeps the no-count path zero-overhead and avoids any conditional branching in the hot path.

adds six commands that round out common string and list operations:

- EXPIREAT key timestamp — set expiry at an absolute unix timestamp (secs)
- PEXPIREAT key timestamp-ms — same but in milliseconds
- GETSET key value — atomically get the old value and set a new one; clears any existing ttl
- MSETNX key value [...] — set multiple keys only if none of them already exist (all-or-nothing)
- LPOP key [count] — without count returns a bulk string; with count returns an array
- RPOP key [count] — same semantics as LPOP with count

implementation notes:
- EXPIREAT/PEXPIREAT convert unix epoch to ember's internal monotonic clock via
  a new unix_ms_to_monotonic_ms() helper (inverse of the existing monotonic_to_unix_ms)
- both functions share a ClockAnchor captured at process start for coherent round-trips
- EXPIREAT is persisted to AOF as Pexpireat (absolute ms); recovery converts to remaining TTL
- MSETNX uses a two-phase fan-out: check existence across all shards, then write all pairs
  if none exist. mirrors redis cluster semantics where MSETNX pairs must share a hash slot
- LPOP/RPOP count uses separate shard request variants (LPopCount/RPopCount) to keep the
  response type distinct (array vs bulk string) without adding conditional logic on the hot path

577 unit tests pass; 12 new integration tests added
@kacy
kacy merged commit 77ec63a into main Feb 26, 2026
5 of 7 checks passed
@kacy
kacy deleted the feat/expireat-getset-msetnx-lpop-count branch February 26, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant